/*****************************************************************************
* 采用简易Lut管理
* 数据的写入必须以块为单位，也就是外部必须有完整的缓存，缓存可以是 其他flash或者ram。
* 写入时发现坏块就直接重写，连续3次判定为坏块标记为坏块不在使用，查询新块重新从缓存写入。
* 使用新的物理块是先擦除并做空检测，不通过直接标记为坏块。
*****************************************************************************/
#include "nand_lut.h"
#include "mytype.h"
#include "string.h"
#include "sys_init.h"
#include "stdlib.h"

nand_attriute nand_dev;

/*****************************************************************************
* @brief   外部支持
*****************************************************************************/
char nand_power_wait(void);
char nand_lut_dev_link(void);

/*****************************************************************************
* @brief   INFO
*****************************************************************************/
#define DEFAULT_PAGE_MAIN_SIZE               2048
#define DEFAULT_PAGE_SPARE_SIZE              64
#define DEFAULT_PAGE_SIZE                    (DEFAULT_PAGE_MAIN_SIZE + DEFAULT_PAGE_SPARE_SIZE)
#define DEFAULT_PAGE_PER_BLOCK               64            
#define DEFAULT_BLOCK_TOTAL                  1024

/*****************************************************************************
* @brief   打开LUT
*****************************************************************************/
void nand_lut_open(void)
{
	// 默认值
	nand_dev.page_size = DEFAULT_PAGE_SIZE;
	nand_dev.page_main_size = DEFAULT_PAGE_MAIN_SIZE;
	nand_dev.page_spare_size = DEFAULT_PAGE_SPARE_SIZE;
	nand_dev.page_per_block = DEFAULT_PAGE_PER_BLOCK;
	nand_dev.block_per_plane = DEFAULT_BLOCK_TOTAL;
	nand_dev.block_total = DEFAULT_BLOCK_TOTAL;
	nand_dev.logic_block_amount = DEFAULT_BLOCK_TOTAL*7/8;
    
	// 链接设备
	nand_lut_dev_link();
	
	// 申请内存
	if(nand_dev.lut)
	{
		free(nand_dev.lut);
	}
			
	if(nand_dev.buff)
	{
		free(nand_dev.buff);
	}
	
	nand_dev.lut = malloc(nand_dev.logic_block_amount * 2 + 16);
	nand_dev.buff = malloc(nand_dev.page_main_size + nand_dev.page_spare_size);
	
    //初始化
    nand_dev.nand_opt.init();

    //创建lut
    nand_lut_creat();
}

/*****************************************************************************
* @brief   关闭LUT
*****************************************************************************/
void nand_lut_close(void)
{
	// 释放内存
	
	if(nand_dev.lut)
	{
		free(nand_dev.lut);
	}
	
	if(nand_dev.buff)
	{
		free(nand_dev.buff);
	}
	
	nand_dev.lut = 0;
}

/*****************************************************************************
* @brief   创建逻辑表
*****************************************************************************/
void nand_lut_creat(void)
{
    uint16_t physical_block;
	lut_spare_data_type * spare;
	
	// 清空LUT
    memset(nand_dev.lut, 0xFF, nand_dev.logic_block_amount * 2);

	// 初始参数
	nand_dev.new_block_next = 0xFFFF;
	nand_dev.empty_block = 0;
	nand_dev.bad_block = 0;
	
	// 备份区指针
	spare = (lut_spare_data_type *)nand_dev.buff;
	
    for (physical_block = 0; physical_block < nand_dev.block_total; physical_block++)
    {
        uint8_t read_cnt;
		
        // 块检测，如果连续5次错误视为坏块，跳过
		read_cnt = 5;
		
        while(read_cnt --)
        {
            // 读取块第一页信息区
            nand_dev.nand_opt.read_page(physical_block * nand_dev.page_per_block);

            nand_dev.nand_opt.read_cache(nand_dev.page_main_size, (uint8_t *) spare, 8);

            // 检测坏块标记
            if (spare->bad_mark != 0xFFFF)
			{
				if(read_cnt == 0)
				{
					nand_dev.bad_block++;
				}
                continue;
			}
			
            // 检测逻辑分块号
            if (spare->logic1 != spare->logic2)
			{
				// 一直不一致则擦除该块
				if(0 == read_cnt)
				{
					//nand_dev.empty_block ++;
					//nand_dev.nand_opt.block_erase(physical_block);
					nand_dev.bad_block++;
				}
                continue;
			}

            // 计算逻辑块号
            if (spare->logic1 < nand_dev.logic_block_amount)
            {
				// 有效逻辑块号
				// 防止一个逻辑号占用多个物理号，脏盘可能出现
				if(nand_dev.lut[spare->logic1] != 0xFFFF)
				{
					// nand_dev.nand_opt.block_erase(nand_dev.lut[lgr1]);
				}
				
				nand_dev.lut[spare->logic1] = physical_block;
				
				// 指向最后一个空块
				nand_dev.new_block_next = physical_block + 1;
				if(nand_dev.new_block_next >= nand_dev.block_total)
					nand_dev.new_block_next = 0;
            }
			else
			{
				if(spare->logic1 == 0xFFFF)
				{
					nand_dev.empty_block ++;
				}
				else
				{
					// 逻辑号越界错误，擦除
					// nand_dev.nand_opt.block_erase(physical_block);
					nand_dev.bad_block++;
				}
			}

            break;
        }
    }
}

/*****************************************************************************
* @brief   nand lut get new block
*****************************************************************************/
uint16_t nand_lut_get_new_block(void)
{
    uint32_t physical_block_addr, cnt;
    uint32_t buff[3];

    //从0块开始搜索
	physical_block_addr = nand_dev.new_block_next;
	
    for (cnt = 0; physical_block_addr < nand_dev.block_total; physical_block_addr++, cnt++)
    {
		// 地址范围
		physical_block_addr %= nand_dev.block_total;
		
        // 执行读操作，检测ECC
        nand_dev.nand_opt.read_page( physical_block_addr * nand_dev.page_per_block);

        // 读取cache， spare部分的前8字节
        nand_dev.nand_opt.read_cache( nand_dev.page_main_size, (uint8_t *)buff, 8);

        // 判断是否坏块，或者已分配 全0xFF代表空闲块
        if ((buff[0] == UINT32_MAX) && (buff[1] == UINT32_MAX))
        {
			// 指向当前位置的下一个
			nand_dev.new_block_next = (physical_block_addr + 1) % nand_dev.block_total;
			nand_dev.empty_block--;
			
			// 返回目标
            return physical_block_addr;
        }
    }

    return 0xFFFF;
}

/*****************************************************************************
* @brief   nand lut logic erase
*****************************************************************************/
uint16_t nand_lut_logic_block_erase(uint16_t logic_block)
{
	nand_dev.nand_opt.block_erase( nand_dev.lut[logic_block]);
	
	return 0;
}

/*****************************************************************************
* @brief   nand lut get new block
*****************************************************************************/
uint16_t nand_lut_logic_block_req(uint16_t logic_block)
{
	if (nand_dev.lut[logic_block] == 0xFFFF)
	{
		uint16_t blk;

		//获取一个新的物理块
		blk = nand_lut_get_new_block();

		if (blk < nand_dev.block_total)
		{
			//块号合法
			nand_dev.lut[logic_block] = blk;
		}
		else
		{
			//块号非法，说明没有可用物理块
			return 1;
		}
	}
	
	return 0;
}

/*****************************************************************************
* @brief   nand lut bad block mark
*****************************************************************************/
uint16_t nand_lut_bad_block_mark(uint16_t physical_block)
{
    int cnt;

    //擦除
    nand_dev.nand_opt.block_erase( physical_block);

    //填充数据
    for (cnt = 0; cnt < (nand_dev.page_main_size + 8); cnt++)
    {
        nand_dev.buff[cnt] = 0;
    }

    //写缓存
    nand_dev.nand_opt.write_cache( 0, nand_dev.buff, nand_dev.page_main_size + 8);

    //执行写入
    nand_dev.nand_opt.program_exe( physical_block * nand_dev.page_per_block);

	nand_dev.bad_block++;
	
    return 0;
}

/*****************************************************************************
*  nand lut get new block
*  return : 0 ok,1 eccerr, -1 ext error
*****************************************************************************/
int8_t nand_lut_program_logic_page(
    uint16_t logic_block,
    uint16_t page_offset,
    uint8_t *buff)
{
    uint16_t cnt;
    uint8_t rbuff[16];
    uint32_t physical_page;
	lut_spare_data_type * spare;
	
    //获取物理页地址
    physical_page = nand_dev.lut[logic_block] * nand_dev.page_per_block + page_offset;

    //写数据到cache
    if (0 == page_offset)
    {
        //块首页写入块信息
		spare = (lut_spare_data_type * )(&buff[nand_dev.page_main_size]);
		spare->bad_mark = 0xFFFF;
		spare->lrc = 0xFFFF;
		spare->logic1 = logic_block;
		spare->logic2 = logic_block;

        for (cnt = 0; cnt < 5; cnt++)
        {
            //写缓存
            nand_dev.nand_opt.write_cache( 0, buff, nand_dev.page_main_size + 8);

            //读回信息区
            nand_dev.nand_opt.read_cache( nand_dev.page_main_size, rbuff, 8);

            //比对
            if (0 == memcmp(rbuff, &buff[nand_dev.page_main_size], 8))
            {
                break;
            }
        }

        //写缓存失败
        if (cnt >= 5)
            return 1;
    }
    else
    {
        nand_dev.nand_opt.write_cache( 0, buff, nand_dev.page_main_size);
    }

    // 执行写操作
    nand_dev.nand_opt.program_exe( physical_page);

	// ECC校验
    for (cnt = 0; cnt < 3; cnt++)
    {
        //执行读操作，检测ECC
        nand_dev.nand_opt.read_page( physical_page);
        if (0 == nand_dev.nand_opt.ecc_error_get())
        {
            break;
        }
    }

    //ecc 错误处理
    if (cnt >= 3)
    {
        return 1;
    }

    return 0;
}

/*****************************************************************************
* @brief   block write
*****************************************************************************/
uint8_t nand_lut_block_write(
    uint16_t logic_block,
    uint8_t(*write_dat_get)(uint32_t addr_offset, uint8_t *pdat, uint16_t len))
{
    uint16_t page_offset;
    uint8_t err;
	uint8_t wloop;

    if (logic_block >= nand_dev.logic_block_amount)
        return 1;

    err = 0;
	
	wloop = 0;
	
    while (wloop < nand_dev.block_total)
    {
        //检测或开辟新块
		if(nand_lut_logic_block_req(logic_block))
		{
			return 1;
		}
		
        //擦除块
		nand_lut_logic_block_erase(logic_block);
        
        //一页一页写入数据
        page_offset = nand_dev.page_per_block;
		
        while (page_offset--)
        {
            int8_t wr_err;

            nand_power_wait();

			//获取要写入的数据
			if ( 0 == write_dat_get(page_offset * nand_dev.page_main_size, nand_dev.buff, nand_dev.page_main_size))
			{
				wr_err = nand_lut_program_logic_page(logic_block, page_offset, nand_dev.buff);
			}
			else
			{
				wr_err = -1;
			}
			
			// 错误检查
            if (1 == wr_err)
            {
                //ecc 错误，准备重写
                err++;
                break;
            }
            else if (-1 == wr_err)
            {
                //外部数据错误，退出
                return 1;
            }
        }

        //错误检查
        if (err)
        {
            //连续错误，标记坏块，并链接新的物理块
            if (err > 5)							
            {
                nand_lut_bad_block_mark(nand_dev.lut[logic_block]);
                nand_dev.lut[logic_block] = 0xFFFF; //设为0xFFFF,准备开始新块链接
                err = 0;                            //重新开始错误计数
				wloop ++;
            }
        }
        else
        {
            //无错误，表示操作成功，退出
            break;
        }

    }

    return 0;
}

/*****************************************************************************
* @brief   nand page write
*****************************************************************************/
uint8_t nand_lut_page_write(uint32_t logic_page_start, uint8_t *pdat, uint16_t page_len)
{
    uint16_t page_cnt;
    uint32_t physical_page, logic_page;
    uint32_t logic_block, page_offset;

    for (page_cnt = 0; page_cnt < page_len; page_cnt++)
    {
        // 地址范围与读取长度检测
        if (logic_page_start + page_cnt >= nand_dev.logic_block_amount * nand_dev.page_per_block)
        {
            return 1;
        }        
        
		// 地址计算
        logic_page = logic_page_start + page_cnt;			//逻辑页地址计算
        logic_block = logic_page / nand_dev.page_per_block;	//逻辑块地址计算
        page_offset = logic_page % nand_dev.page_per_block;	//块内页地址偏移

		// 块内首页-新块检测
		if(0 == page_offset)
		{
			//检测或开辟新块
			if(nand_lut_logic_block_req(logic_block))
			{
				return 1;
			}
			
			//擦除块
			if(nand_lut_logic_block_erase(logic_block))
			{
				return 1;
			}
		}
		
		// 逻辑页编程
		if(nand_lut_program_logic_page(logic_block, page_offset, pdat))
		{
			// 如果失败则擦除块
			nand_lut_logic_block_erase(logic_block);
			nand_dev.lut[logic_block] = 0xFFFF;
			return 1;
		}
		
		// 数据地址增量
        pdat += nand_dev.page_main_size;
    }

	return 0;
}

/*****************************************************************************
* @brief   nand read
*****************************************************************************/
uint8_t nand_lut_page_read(uint32_t logic_page_start, uint8_t *pdat, uint16_t page_len)
{
    uint16_t page_cnt;

    uint32_t physical_page, logic_page_addr;
    uint32_t logic_block, page_offset;

    for (page_cnt = 0; page_cnt < page_len; page_cnt++)
    {
        //地址范围与读取长度检测
        if (logic_page_start + page_cnt >= nand_dev.logic_block_amount * nand_dev.page_per_block)
        {
            memset(pdat, 0xff, nand_dev.page_main_size);
            continue;
        }        
        
        logic_page_addr = logic_page_start + page_cnt; //逻辑页地址计算

        logic_block = logic_page_addr / nand_dev.page_per_block;	//逻辑块地址计算

        if (nand_dev.lut[logic_block] >= nand_dev.block_total)		//物理块地址合法性检查
        {
            //非法页，填充空数据
            memset(pdat, 0xff, nand_dev.page_main_size);
            pdat += nand_dev.page_main_size;
            continue;
        }

        page_offset = logic_page_addr % nand_dev.page_per_block;		//块内页地址偏移

        physical_page = nand_dev.lut[logic_block] * nand_dev.page_per_block + page_offset; //物理页地址

        //执行读操作，检测ECC
        nand_dev.nand_opt.read_page( physical_page);
        if (0 == nand_dev.nand_opt.ecc_error_get())
        {
            //ecc 校验错误
        }

        //读取cache
        nand_dev.nand_opt.read_cache( 0, pdat, nand_dev.page_main_size);
        pdat += nand_dev.page_main_size;
    }

    return 0;
}

/*****************************************************************************
* @brief   page read
*****************************************************************************/
uint8_t nand_lut_inp_read(uint32_t logic_page, uint16_t offset, uint16_t len, uint8_t *pdat)
{
    uint16_t page_cnt;

    uint32_t physical_page;
    uint32_t logic_block, page_offset;

    if(len > nand_dev.page_main_size)
    {
        len = nand_dev.page_main_size;
    }
    
    //地址范围与读取长度检测
    if (logic_page > nand_dev.logic_block_amount * nand_dev.page_per_block)
    {
        memset(pdat, 0xff, len);
        return 1;
    }

    logic_block = logic_page / nand_dev.page_per_block;		//逻辑块地址计算

    if (nand_dev.lut[logic_block] >= nand_dev.block_total)	//物理块地址合法性检查
    {
        //非法页，填充空数据
        memset(pdat, 0xff, len);
        return 1;
    }

    page_offset = logic_page % nand_dev.page_per_block;		//块内页地址偏移

    physical_page = nand_dev.lut[logic_block] * nand_dev.page_per_block + page_offset; //物理页地址

    // 执行读操作，检测ECC
    nand_dev.nand_opt.read_page( physical_page);
    if (0 == nand_dev.nand_opt.ecc_error_get())
    {
        //ecc 校验错误
    }

	// 长度检测
	if(len + offset > nand_dev.page_main_size)
	{
		if(nand_dev.page_main_size > offset)
		{
			len = nand_dev.page_main_size - offset;
		}
		else
		{
			len = 0;
		}
	}
	
    // 读取cache
	if(len)
	{
		nand_dev.nand_opt.read_cache( offset, pdat, len);
	}

    return 0;
}
